/*Arduino BREAKOUT Game on 8x8 Matrix WS2812b by mircemk, June 2025 */ #include #define LED_PIN 6 #define NUM_LEDS 64 #define MATRIX_WIDTH 8 #define MATRIX_HEIGHT 8 #define LEFT_BUTTON_PIN 9 #define RIGHT_BUTTON_PIN 10 #define BUZZER_PIN 2 // Game constants #define MIN_X_SPEED 0.2 #define MAX_X_SPEED 0.35 #define MIN_Y_SPEED 0.25 #define MAX_Y_SPEED 0.4 #define SPEED_DECAY 0.98 // Sound settings #define TONE_HIT 1200 #define TONE_PADDLE 800 #define TONE_WALL 1000 #define TONE_GAMEOVER 400 #define TONE_DURATION 15 #define TONE_COOLDOWN 50 CRGB leds[NUM_LEDS]; // Game elements int paddlePos = 3; int paddleWidth = 3; float ballX = 4; float ballY = 6; float ballSpeedX = 0.2; float ballSpeedY = -0.25; bool bricks[3][8]; CRGB brickColors[3][8]; int score = 0; int level = 1; bool gameOver = false; bool levelCompleted = false; bool newGame = false; bool displayScoreDone = false; // Timing control unsigned long lastFrameTime = 0; unsigned long lastSoundTime = 0; const unsigned long FRAME_TIME = 50; const byte SMILEY[8] = { B00111100, B01000010, B10100101, B10000001, B10100101, B10011001, B01000010, B00111100 }; // Font definition for letters and numbers (5x7 font) const byte font[37][5] = { {0x7C, 0x22, 0x22, 0x22, 0x7C}, // A {0x7E, 0x4A, 0x4A, 0x4A, 0x34}, // B {0x3C, 0x42, 0x42, 0x42, 0x24}, // C {0x7E, 0x42, 0x42, 0x42, 0x3C}, // D {0x7E, 0x4A, 0x4A, 0x4A, 0x42}, // E {0x7E, 0x0A, 0x0A, 0x0A, 0x02}, // F {0x3C, 0x42, 0x52, 0x52, 0x34}, // G {0x7E, 0x08, 0x08, 0x08, 0x7E}, // H {0x00, 0x42, 0x7E, 0x42, 0x00}, // I {0x20, 0x40, 0x42, 0x3E, 0x02}, // J {0x7E, 0x08, 0x14, 0x22, 0x42}, // K {0x7E, 0x40, 0x40, 0x40, 0x40}, // L {0x7E, 0x04, 0x18, 0x04, 0x7E}, // M {0x7E, 0x04, 0x08, 0x10, 0x7E}, // N {0x3C, 0x42, 0x42, 0x42, 0x3C}, // O {0x7E, 0x12, 0x12, 0x12, 0x0C}, // P {0x3C, 0x42, 0x52, 0x22, 0x5C}, // Q {0x7E, 0x12, 0x32, 0x52, 0x0C}, // R {0x24, 0x4A, 0x4A, 0x4A, 0x30}, // S {0x02, 0x02, 0x7E, 0x02, 0x02}, // T {0x3E, 0x40, 0x40, 0x40, 0x3E}, // U {0x1E, 0x20, 0x40, 0x20, 0x1E}, // V {0x3E, 0x40, 0x30, 0x40, 0x3E}, // W {0x66, 0x18, 0x18, 0x18, 0x66}, // X {0x06, 0x08, 0x70, 0x08, 0x06}, // Y {0x62, 0x52, 0x4A, 0x46, 0x42}, // Z {0x3E, 0x51, 0x49, 0x45, 0x3E}, // 0 {0x00, 0x42, 0x7F, 0x40, 0x00}, // 1 {0x72, 0x49, 0x49, 0x49, 0x46}, // 2 {0x21, 0x41, 0x45, 0x4B, 0x31}, // 3 {0x18, 0x14, 0x12, 0x7F, 0x10}, // 4 {0x27, 0x45, 0x45, 0x45, 0x39}, // 5 {0x3E, 0x49, 0x49, 0x49, 0x32}, // 6 {0x01, 0x71, 0x09, 0x05, 0x03}, // 7 {0x36, 0x49, 0x49, 0x49, 0x36}, // 8 {0x26, 0x49, 0x49, 0x49, 0x3E}, // 9 {0x00, 0x00, 0x00, 0x00, 0x00} // Space }; // Function to get the index of a character in the font array int getCharIndex(char c) { if (c >= 'A' && c <= 'Z') { return c - 'A'; } else if (c >= '0' && c <= '9') { return c - '0' + 26; } else { return 36; // Space } } // Function to scroll text on the LED matrix void scrollText(const char* text, int delayTime) { int length = strlen(text); for (int offset = 0; offset < length * 6 + MATRIX_WIDTH; offset++) { fill_solid(leds, NUM_LEDS, CRGB::Black); for (int i = 0; i < length; i++) { int charIndex = getCharIndex(text[i]); for (int col = 0; col < 5; col++) { if (i * 6 + col - offset >= 0 && i * 6 + col - offset < MATRIX_WIDTH) { byte column = font[charIndex][col]; for (int row = 0; row < 7; row++) { if (column & (1 << row)) { leds[getPixelIndex(i * 6 + col - offset, row)] = CRGB::White; } } } } } FastLED.show(); delay(delayTime); } } void setup() { FastLED.addLeds(leds, NUM_LEDS); FastLED.setBrightness(50); pinMode(LEFT_BUTTON_PIN, INPUT_PULLUP); pinMode(RIGHT_BUTTON_PIN, INPUT_PULLUP); pinMode(BUZZER_PIN, OUTPUT); delay (1000); scrollText(" MINI BREAKOUT", 80); initializeGame(); } void playSound(int frequency, int duration) { noTone(BUZZER_PIN); // Ensure the buzzer is off before playing a new sound tone(BUZZER_PIN, frequency, duration); delay(duration); noTone(BUZZER_PIN); // Turn off the buzzer after the duration } void initializeGame() { FastLED.setBrightness(50); // Ensure consistent brightness for (int row = 0; row < 3; row++) { for (int col = 0; col < 8; col++) { bricks[row][col] = true; brickColors[row][col] = CHSV(random(0, 255), 255, 255); // Random color } } paddlePos = 3; ballX = 4.0; ballY = 6.0; ballSpeedX = 0.2; ballSpeedY = -0.25; score = 0; level = 1; paddleWidth = 3; gameOver = false; levelCompleted = false; newGame = false; displayScoreDone = false; lastFrameTime = millis(); lastSoundTime = 0; } void initializeLevel() { for (int row = 0; row < 3; row++) { for (int col = 0; col < 8; col++) { bricks[row][col] = true; brickColors[row][col] = CHSV(random(0, 255), 255, 255); // Random color } } paddlePos = 3; ballX = 4.0; ballY = 6.0; ballSpeedX = 0.2; ballSpeedY = -0.25; levelCompleted = false; lastFrameTime = millis(); } void loop() { unsigned long currentTime = millis(); if (!gameOver && !newGame) { if (currentTime - lastFrameTime >= FRAME_TIME) { handleInput(); updateGame(); lastFrameTime = currentTime; } drawGame(); FastLED.show(); } else if (gameOver && !displayScoreDone) { char scoreText[20]; sprintf(scoreText, "SCORE: %d", score); scrollText(scoreText, 100); displayScoreDone = true; displaySmiley(); } else if (gameOver && displayScoreDone) { if (digitalRead(LEFT_BUTTON_PIN) == LOW || digitalRead(RIGHT_BUTTON_PIN) == LOW) { delay(300); initializeGame(); newGame = false; // Reset newGame for the next game cycle displayScoreDone = false; } } } void handleInput() { if (digitalRead(LEFT_BUTTON_PIN) == LOW && paddlePos > 0) { paddlePos--; } if (digitalRead(RIGHT_BUTTON_PIN) == LOW && paddlePos < MATRIX_WIDTH - paddleWidth) { paddlePos++; } } void updateGame() { ballX += ballSpeedX; ballY += ballSpeedY; // Brick collisions bool allBricksDestroyed = true; for (int row = 0; row < 3; row++) { for (int col = 0; col < 8; col++) { if (bricks[row][col]) { allBricksDestroyed = false; if (ballY >= row - 0.1 && ballY < row + 1.1 && ballX >= col - 0.1 && ballX < col + 1.1) { bricks[row][col] = false; float dx = ballX - (col + 0.5); float dy = ballY - (row + 0.5); if (abs(dx) > abs(dy)) { ballSpeedX = -ballSpeedX; } else { ballSpeedY = -ballSpeedY; } ballSpeedX += (random(-15, 16) / 100.0); ballSpeedX = constrain(ballSpeedX, -MAX_X_SPEED, MAX_X_SPEED); ballSpeedY = constrain(ballSpeedY, -MAX_Y_SPEED, MAX_Y_SPEED); score += 1; playSound(TONE_HIT, TONE_DURATION); break; } } } } // Check for level completion if (allBricksDestroyed) { levelCompleted = true; level++; if (level > 3) { gameOver = true; playSound(TONE_GAMEOVER, TONE_DURATION * 5); } else { if (level == 2) { paddleWidth = 2; } else if (level == 3) { paddleWidth = 1; } initializeLevel(); } } // Wall collisions if (ballX <= 0 || ballX >= MATRIX_WIDTH - 1) { ballSpeedX = -ballSpeedX; ballX = (ballX <= 0) ? 0.1 : (MATRIX_WIDTH - 1.1); if (abs(ballSpeedY) < MIN_Y_SPEED) { ballSpeedY += (ballSpeedY > 0 ? MIN_Y_SPEED : -MIN_Y_SPEED) * 0.5; } playSound(TONE_WALL, TONE_DURATION); } if (ballY <= 0) { ballSpeedY = abs(ballSpeedY); ballY = 0.1; playSound(TONE_WALL, TONE_DURATION); } // Paddle collision if (ballY >= MATRIX_HEIGHT - 2 && ballY < MATRIX_HEIGHT - 1) { if (ballX >= paddlePos && ballX < paddlePos + paddleWidth) { ballY = MATRIX_HEIGHT - 2; float hitPos = (ballX - paddlePos) / paddleWidth; float angle = (hitPos - 0.5) * PI * 0.7; float speed = sqrt(ballSpeedX * ballSpeedX + ballSpeedY * ballSpeedY); ballSpeedX = sin(angle) * speed; ballSpeedY = -abs(cos(angle) * speed); if (abs(ballSpeedY) < MIN_Y_SPEED) { ballSpeedY = -MIN_Y_SPEED; } playSound(TONE_PADDLE, TONE_DURATION); } } // Check if the ball collapses next to paddle on the left or right side if (ballY >= MATRIX_HEIGHT - 1 && (ballX < paddlePos || ballX >= paddlePos + paddleWidth)) { gameOver = true; playSound(TONE_GAMEOVER, TONE_DURATION * 5); } ballSpeedX *= SPEED_DECAY; ballSpeedY *= SPEED_DECAY; if (abs(ballSpeedX) < MIN_X_SPEED) { ballSpeedX = (ballSpeedX < 0) ? -MIN_X_SPEED : MIN_X_SPEED; } if (abs(ballSpeedY) < MIN_Y_SPEED) { ballSpeedY = (ballSpeedY < 0) ? -MIN_Y_SPEED : MIN_Y_SPEED; } ballSpeedX = constrain(ballSpeedX, -MAX_X_SPEED, MAX_X_SPEED); ballSpeedY = constrain(ballSpeedY, -MAX_Y_SPEED, MAX_Y_SPEED); if (ballY >= MATRIX_HEIGHT) { gameOver = true; playSound(TONE_GAMEOVER, TONE_DURATION * 5); } } void drawGame() { fill_solid(leds, NUM_LEDS, CRGB::Black); // Draw bricks for (int row = 0; row < 3; row++) { for (int col = 0; col < 8; col++) { if (bricks[row][col]) { leds[getPixelIndex(col, row)] = brickColors[row][col]; } } } // Draw paddle for (int i = 0; i < paddleWidth; i++) { leds[getPixelIndex(paddlePos + i, MATRIX_HEIGHT - 1)] = CRGB::Blue; } // Draw ball leds[getPixelIndex(int(ballX), int(ballY))] = CRGB::White; } void displaySmiley() { fill_solid(leds, NUM_LEDS, CRGB::Black); for (int row = 0; row < 8; row++) { for (int col = 0; col < 8; col++) { if (SMILEY[row] & (1 << (7 - col))) { leds[getPixelIndex(col, row)] = CRGB::Yellow; } } } FastLED.show(); } int getPixelIndex(int x, int y) { return y * MATRIX_WIDTH + x; }